package tme.kvstore;

import java.util.ArrayList;
import java.util.UUID;

import oracle.kv.*;

/**
 * @author kenza ABDELAZIZ
 * @author Jianguo HAO
 * */
public class EX_2 {

	private final KVStore store;

	/**
	 * The main function. We could call one method by removing and adding
	 * comment.
	 * */
	public static void main(String args[]) {
		try {
			EX_2 a = new EX_2(args);
			// a.A1();
			// a.A2();
			// a.M1();
			// a.M2();
			a.M2_coherence();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private EX_2(String[] argv) {

		String storeName = "kvstore";
		String hostName = "localhost";
		// String hostName = "ari-31-201-15";
		String hostPort = "5000";

		final int nArgs = argv.length;
		int argc = 0;

		while (argc < nArgs) {
			final String thisArg = argv[argc++];

			if (thisArg.equals("-store")) {
				if (argc < nArgs) {
					storeName = argv[argc++];
				} else {
					usage("-store requires an argument");
				}
			} else if (thisArg.equals("-host")) {
				if (argc < nArgs) {
					hostName = argv[argc++];
				} else {
					usage("-host requires an argument");
				}
			} else if (thisArg.equals("-port")) {
				if (argc < nArgs) {
					hostPort = argv[argc++];
				} else {
					usage("-port requires an argument");
				}
			} else {
				usage("Unknown argument: " + thisArg);
			}
		}

		store = KVStoreFactory.getStore(new KVStoreConfig(storeName, hostName
				+ ":" + hostPort));
	}

	private void usage(String message) {
		System.out.println("\n" + message + "\n");
		System.out.println("usage: " + getClass().getName());
		System.out.println("\t-store <instance name> (default: kvstore) "
				+ "-host <host name> (default: localhost) "
				+ "-port <port number> (default: 5000)");
		System.exit(1);
	}

	/**
	 * The function A1 in Exercise 1
	 * */
	public void A1() {
		for (int i = 0; i < 10000; i++) {
			// Lit la quantite recente de P1
			ValueVersion valueVersion = store.get(Key.createKey("P1"));
			String quantite = new String(valueVersion.getValue().getValue());
			// la imprime
			System.out.println("P1" + " " + quantite);
			// Ajoute 1
			Integer amount = Integer.parseInt(quantite);
			amount++;
			String newValue = String.valueOf(amount);
			// Mise a jour
			store.put(Key.createKey("P1"),
					Value.createValue(newValue.getBytes()));
		}
	}

	/**
	 * The function A2 in Exercise 1
	 * 
	 * utiliser l'ecriture conditionnelle pour controler l'execution simultanee
	 * de plusieurs programmes.
	 * 
	 * voir la methode putIfVersion. La valeur de retour est nulle lorsque
	 * l'ecriture est refusee.
	 * */
	public void A2() {
		for (int i = 0; i < 10000; i++) {
			// Lit la quantite recente de P1
			ValueVersion valueVersion = store.get(Key.createKey("P1"));
			// Obtien la version recente
			Version marchVersion = valueVersion.getVersion();
			String quantite = new String(valueVersion.getValue().getValue());
			// la imprime
			System.out.println("P1" + " " + quantite);
			// Ajoute 1
			Integer amount = Integer.parseInt(quantite);
			amount++;
			String newValue = String.valueOf(amount);
			// Mise a jour sous l'ecriture conditionnelle
			Version nouvelleVersion = store.putIfVersion(Key.createKey("P1"),
					Value.createValue(newValue.getBytes()), marchVersion);
			if (nouvelleVersion == null) {
				// System.out.println("Oops");
				i--;
			}
		}
	}

	/**
	 * The function M1 in Exercise 2
	 * 
	 * Ecrire un programme M1 qui effectue 1000 iterations. A chaque iteration,
	 * on ajoute 1 a la quantite des produits P1 a P5 de la categorie C1.
	 * */
	public void M1() {
		for (int i = 0; i < 1000; i++) {
			for (int j = 1; j <= 5; j++) {
				// Lit la quantite recente de P1
				Key k = Key.createKey("C1", "P" + j);
				ValueVersion valueVersion = store.get(k);
				// Obtien la version recente
				Version marchVersion = valueVersion.getVersion();
				String quantite = new String(valueVersion.getValue().getValue());
				// la imprime
				System.out.println(k.toString() + " " + quantite);
				// Ajoute 1
				Integer amount = Integer.parseInt(quantite);
				amount++;
				String newValue = String.valueOf(amount);
				// Mise a jour sous l'ecriture conditionnelle
				Version nouvelleVersion = store.putIfVersion(
						Key.createKey("C1", "P" + String.valueOf(j)),
						Value.createValue(newValue.getBytes()), marchVersion);
				if (nouvelleVersion == null) {
					// System.out.println("Oops");
					j--;
				}
			}
		}
	}

	public void imprimer() {
		for (int j = 1; j <= 5; j++) {
			// Lit la quantite recente de P1
			Key k = Key.createKey("C1", "P" + j);
			ValueVersion valueVersion = store.get(k);

			String quantite = new String(valueVersion.getValue().getValue());
			// la imprime
			System.out.println(k.toString() + " " + quantite);
		}
	}

	/**
	 * The function M2 in Exercise 1
	 * 
	 * Ecrire un programme M2 qui effectue 1000 iterations. A chaque iteration,
	 * on determine la valeur max qui est la plus grande quantite parmi les
	 * produits P1 a P5 de C1. Puis on affecte la nouvelle quantite = (1 + max)
	 * a tous les produits P1 a P5.
	 * */
	public void M2() {
		// Iterer 1000 fois
		for (int i = 0; i < 1000; i++) {
			int max = 0;
			// Caculer la quantite maximum entre les 5 produits.
			for (int j = 1; j <= 5; j++) {
				Key key = Key.createKey("C1", "P" + j);
				ValueVersion valueVersion = store.get(key);
				Value value = valueVersion.getValue();
				int quantity = Integer.parseInt(new String(value.getValue()));
				if (max < quantity) {
					max = quantity;
				}
			}

			// Mise a jour les quantite par la valeur maximum +1
			for (int k = 1; k <= 5; k++) {
				Key key = Key.createKey("C1", "P" + k);
				store.put(key, Value.createValue(new Integer(max + 1)
						.toString().getBytes()));
			}
		}
		imprimer();
	}

	/**
	 * The coherence function M2 in Exercise 1
	 * 
	 * Proposer une solution pour que M2 conserve la coherence des donnees.
	 * */
	public void M2_coherence() {
		// Iterer 1000 fois
		for (int i = 0; i < 1000; i++) {
			int max = 0;
			Version[] dataVersions = new Version[5];
			// Enregistrer les versions et calculer la quantite maximum entre
			// les 5 produits.
			for (int j = 1; j <= 5; j++) {
				Key key = Key.createKey("C1", "P" + j);
				ValueVersion valueVersion = store.get(key);
				// Enregistrer les version
				dataVersions[j - 1] = valueVersion.getVersion();
				UUID uuid = valueVersion.getVersion().getRepGroupUUID();
				long l = valueVersion.getVersion().getVersion();
				valueVersion.setVersion(new Version(uuid, 1 + l));
				Value value = valueVersion.getValue();
				int quantity = Integer.parseInt(new String(value.getValue()));
				if (max < quantity) {
					max = quantity;
				}
			}

			// Transaction
			// Creer un transaction qui conserve les 5 operations (mise a jour)
			ArrayList<Operation> updateOperations = new ArrayList<Operation>();
			for (int k = 1; k <= 5; k++) {
				Key key = Key.createKey("C1", "P" + k);
				// Creer l'un des 5 operations, si non coherence, jeter un
				// exception d'OperationExecutionException
				Operation o = store.getOperationFactory()
						.createPutIfVersion(
								key,
								Value.createValue(Integer.toString(max + 1)
										.getBytes()),
								new Version(dataVersions[k - 1]
										.getRepGroupUUID(), dataVersions[k - 1]
										.getVersion() + 1),
								ReturnValueVersion.Choice.NONE, true);
				// ajouter l'operation dans la transaction
				updateOperations.add(o);
			}
			try {
				// Execute the transaction, si echec, rollback
				store.execute(updateOperations);
			} catch (DurabilityException e) {
				// echec, rollback
				i--;
				continue;
			} catch (OperationExecutionException e) {
				// echec, rollback
				i--;
				continue;
			} catch (FaultException e) {
				// echec, rollback
				i--;
				continue;
			}
		}
		imprimer();
	}

	public void test() {
		for (int i = 0; i < 200; i++) {
			int max = 0;
			// Lock
			Version[] lockVersions = new Version[5];
			for (int x = 1; x <= 5; x++) {
				ArrayList<String> majorKey = new ArrayList<String>();
				ArrayList<String> minorKey = new ArrayList<String>();
				majorKey.add("C1");
				minorKey.add("P" + x);
				minorKey.add("K" + x);
				Key key = Key.createKey(majorKey, minorKey);
				// System.out.println(key.toString());
				ValueVersion valueVersion = store.get(key);
				// System.out.println(new String(valueVersion.getValue()
				// .getValue()));
				if (new String(valueVersion.getValue().getValue()).equals("0")) {
					lockVersions[x - 1] = store.putIfVersion(key, Value
							.createValue(new Integer(1).toString().getBytes()),
							valueVersion.getVersion());
					// System.out.println(lockVersions[x - 1]);
				} else {
					lockVersions[x - 1] = null;
				}

			}
			boolean isSuccessful = true;
			for (int y = 0; y < 5; y++) {
				if (lockVersions[y] == null) {
					isSuccessful = false;
					break;
				}
			}
			// System.out.println(isSuccessful);
			if (!isSuccessful) {
				i--;
				continue;
			}
			System.out.println(i);
			Version[] dataVersions = new Version[5];
			for (int j = 1; j <= 5; j++) {
				Key key = Key.createKey("C1", "P" + j);
				ValueVersion valueVersion = store.get(key);
				dataVersions[j - 1] = valueVersion.getVersion();
				Value value = valueVersion.getValue();
				int quantity = Integer.parseInt(new String(value.getValue()));
				if (max < quantity) {
					max = quantity;
				}
			}

			ArrayList<Operation> updateOperations = new ArrayList<Operation>();
			for (int k = 1; k <= 5; k++) {
				Key key = Key.createKey("C1", "P" + k);
				Operation o = store.getOperationFactory()
						.createPutIfVersion(
								key,
								Value.createValue(Integer.toString(max + 1)
										.getBytes()), dataVersions[k - 1],
								ReturnValueVersion.Choice.NONE, true);
				updateOperations.add(o);
			}
			try {
				store.execute(updateOperations);
			} catch (DurabilityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (OperationExecutionException e) {
				// TODO Auto-generated catch block
				for (int x = 1; x <= 5; x++) {
					ArrayList<String> majorKey = new ArrayList<String>();
					ArrayList<String> minorKey = new ArrayList<String>();
					majorKey.add("C1");
					minorKey.add("P" + x);
					minorKey.add("K" + x);
					Key key = Key.createKey(majorKey, minorKey);

					lockVersions[x - 1] = store.putIfVersion(key, Value
							.createValue(new Integer(0).toString().getBytes()),
							lockVersions[x - 1]);
				}
				i--;
				continue;
			} catch (FaultException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			for (int x = 5; x >= 1; x--) {
				ArrayList<String> majorKey = new ArrayList<String>();
				ArrayList<String> minorKey = new ArrayList<String>();
				majorKey.add("C1");
				minorKey.add("P" + x);
				minorKey.add("K" + x);
				Key key = Key.createKey(majorKey, minorKey);

				lockVersions[x - 1] = store
						.putIfVersion(key, Value.createValue(new Integer(0)
								.toString().getBytes()), lockVersions[x - 1]);
			}
		}
		imprimer();
	}
}